package com.jiamingZhao.wrapper;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;
import com.jiamingZhao.cache.CacheMap;
import com.jiamingZhao.constants.SQLConstant;
import com.jiamingZhao.pojo.info.TableInfo;
import com.jiamingZhao.utils.DbUtil;
import com.jiamingZhao.utils.ListKit;
import com.jiamingZhao.utils.MapKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

public class LambdaQueryWrapper<T> implements Wrapper<T> {
    private static final Logger log = LoggerFactory.getLogger(LambdaQueryWrapper.class);
    private final SqlPara<T> sqlPara;

    private final TableInfo tableInfo;
    private final StringBuilder whereBuilder;
    private List<String> selectFields;
    private final LinkedHashMap<String, Object> updateFields;
    private final List<String> orderByParts;

    private final Class<T> entityClass;
    private List<String> groupByParts;
    private Integer limit;

    /**
     * 直接返回空
     */
    private boolean returnEmpty = false;


    public static <T> LambdaQueryWrapper<T> create(Class<T> clazz) {
        return new LambdaQueryWrapper<>(clazz);
    }



    private LambdaQueryWrapper(Class<T> entityClass) {
        this.sqlPara = new SqlPara<>();
        this.sqlPara.setEntityClass(entityClass);
        this.tableInfo = Objects.requireNonNull(CacheMap.getTableInfo(entityClass));
        this.selectFields = new ArrayList<>();
        this.whereBuilder = new StringBuilder();
        this.orderByParts = new ArrayList<>();
        this.groupByParts = new ArrayList<>();
        this.updateFields = new LinkedHashMap<>();
        this.entityClass = entityClass;
    }

    @SafeVarargs
    public final LambdaQueryWrapper<T> select(SFunction<T, ?>... columnFunctions) {
        selectFields = ListKit.convert(Arrays.asList(columnFunctions), this::getColumn);
        return this;
    }

    public final LambdaQueryWrapper<T> set(SFunction<T, ?> field, Object value) {
        Assert.isFalse(value instanceof Collection, "传入值类型为容器类型，请检查");
        Assert.isFalse(value instanceof Enum, "枚举类型不能作为设置值");
        this.updateFields.put(getColumn(field), value);
        return this;
    }

    public final LambdaQueryWrapper<T> set(SFunction<T, ?> field, SFunction<T, ?> targetField) {
        this.updateFields.put(getColumn(field), targetField);
        return this;
    }

    public final LambdaQueryWrapper<T> select(String... fields) {
        selectFields = Lists.newArrayList(fields);
        return this;
    }

    public final LambdaQueryWrapper<T> addSelect(String field) {
        selectFields.add(field);
        return this;
    }

    @SafeVarargs
    public final LambdaQueryWrapper<T> groupBy(SFunction<T, ?>... columnFunctions) {
        this.groupByParts = ListKit.convert(Arrays.asList(columnFunctions), this::getColumn);
        return this;
    }

    public LambdaQueryWrapper<T> addDateCond(SFunction<T, ?> columnFunction, Date startTime, Date endTime) {
        String fieldName = getColumn(columnFunction);
        if (Objects.nonNull(startTime)) {
            whereBuilder.append(StrUtil.format(" and {} >= ? ", fieldName));
            sqlPara.addPara(DateUtil.beginOfDay(startTime));
        }
        if (Objects.nonNull(endTime)) {
            whereBuilder.append(StrUtil.format(" and {} <= ? ", fieldName));
            sqlPara.addPara(DateUtil.endOfDay(endTime));
        }
        return this;
    }

    public LambdaQueryWrapper<T> addCondIfValue(SFunction<T, ?> columnFunction, Object fieldValue) {
        if (Objects.isNull(fieldValue)) {
            return this;
        }
        return addCond(getColumn(columnFunction), fieldValue);
    }

    public LambdaQueryWrapper<T> eq(SFunction<T, ?> columnFunction, Object value) {
        if (value instanceof Enum) {
            throw new IllegalArgumentException("枚举类型不能作为查询条件");
        }
        if (value instanceof Collection) {
            throw new IllegalArgumentException("传入参数不能为集合");
        }
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" = ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> eq(boolean query, SFunction<T, ?> columnFunction, Object value) {
        if (query) {
            return eq(columnFunction, value);
        }
        return this;
    }

    public LambdaQueryWrapper<T> nq(SFunction<T, ?> columnFunction, Object value) {
        return nq(getColumn(columnFunction), value);
    }

    public LambdaQueryWrapper<T> nq(String  field, Object value) {
        whereBuilder.append(" and `").append(field).append("` != ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> nq(SFunction<T, ?> columnFunction, SFunction<T, ?> targetField) {
        whereBuilder.append(" and `").append(getColumn(columnFunction)).append("` != `").append(getColumn(targetField))
                .append("`");
        return this;
    }


    public LambdaQueryWrapper<T> gt(SFunction<T, ?> columnFunction, SFunction<T, ?> columnFunction2) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" > ").append(getColumn(columnFunction2));
        return this;
    }

    public LambdaQueryWrapper<T> gt(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" > ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> ge(boolean query, SFunction<T, ?> columnFunction, Object value) {
        if (query) {
            return ge(columnFunction, value);
        }
        return this;
    }

    public LambdaQueryWrapper<T> ge(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" >= ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> le(boolean query, SFunction<T, ?> columnFunction, Object value) {
        if (query) {
            return le(columnFunction, value);
        }
        return this;
    }

    public LambdaQueryWrapper<T> le(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" <= ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> lt(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" < ?");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> between(SFunction<T, ?> columnFunction, Object startObj, Object endObj) {
        gt(columnFunction, startObj).le(columnFunction, endObj);
        return this;
    }

    public LambdaQueryWrapper<T> like(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" like concat('%',?,'%')");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> likeRight(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" like concat(?,'%')");
        sqlPara.addPara(value);
        return this;
    }

    public LambdaQueryWrapper<T> likeLeft(SFunction<T, ?> columnFunction, Object value) {
        whereBuilder.append(" and ").append(getColumn(columnFunction)).append(" like concat('%', ?)");
        sqlPara.addPara(value);
        return this;
    }

    /**
     * 尝试添加IN条件，如果字段值列表为空，则不进行任何操作。
     *
     * @param columnFunction 字段Lambda
     * @param values         值
     */
    public LambdaQueryWrapper<T> tryIn(SFunction<T, ?> columnFunction, Collection<?> values) {
        // 检查字段值列表是否为空，如果为空则返回当前实例，不进行任何操作。
        if (ObjectUtil.isEmpty(values)) {
            return this;
        }
        return in(columnFunction, values);
    }

    public LambdaQueryWrapper<T> in(boolean query, SFunction<T, ?> columnFunction, Collection<?> values) {
        if (query) {
            return in(columnFunction, values);
        }
        return this;
    }

    public LambdaQueryWrapper<T> in(SFunction<T, ?> columnFunction, Collection<?> values) {
        String fieldName = getColumn(columnFunction);
        if (ObjectUtil.isEmpty(values)) {
            // 如果为空，则预期应是查询为空
            this.returnEmpty = true;
            log.warn("字段{}的值参数传值为空，预期查询结果为空，直接返回空数据。", fieldName);
            return this;
        }
        // 追加AND关键字和IN条件的起始部分到SQL语句中。
        whereBuilder.append(" and ").append(fieldName).append(" in( ");
        // 使用ListKit的getFieldStr方法将字段值列表转换为占位符字符串（如"?","?","?"），并追加到SQL语句中。
        whereBuilder.append(ListKit.getFieldStr(Lists.newArrayList(values), e -> "?")).append(" ) ");
        // 遍历字段值列表，将每个值作为参数追加到参数列表中。
        values.forEach(sqlPara::addPara);
        // 返回当前实例，允许链式调用。
        return this;
    }

    public LambdaQueryWrapper<T> notIn(SFunction<T, ?> columnFunction, Collection<?> values) {
        String fieldName = getColumn(columnFunction);
        if (ObjectUtil.isEmpty(values)) {
            // 如果为空，则预期应是查询为空
            this.returnEmpty = true;
            log.warn("字段{}的值参数传值为空，预期查询结果为空，直接返回空数据。", fieldName);
            return this;
        }
        // 追加AND关键字和IN条件的起始部分到SQL语句中。
        whereBuilder.append(" and ").append(fieldName).append(" not in( ");
        // 使用ListKit的getFieldStr方法将字段值列表转换为占位符字符串（如"?","?","?"），并追加到SQL语句中。
        whereBuilder.append(ListKit.getFieldStr(Lists.newArrayList(values), e -> "?")).append(" ) ");
        // 遍历字段值列表，将每个值作为参数追加到参数列表中。
        values.forEach(sqlPara::addPara);
        // 返回当前实例，允许链式调用。
        return this;
    }


    private LambdaQueryWrapper<T> addCond(String fieldName, Object fieldValue) {
        if (fieldValue instanceof Collection) {
            throw new IllegalArgumentException("传入值类型为容器类型，请检查");
        }
        return addCondIfValue(fieldValue, (sqlBuilder, sqlPart) -> {
            if (Objects.equals(fieldValue, SQLConstant.NULL)) {
                whereBuilder.append(String.format(" and %s is null", fieldName));
            } else {
                whereBuilder.append(String.format(" and %s = ?", fieldName));
                sqlPara.addPara(fieldValue);
            }
        });
    }

    public LambdaQueryWrapper<T> addCondIfValue(Object fieldValue, BiConsumer<StringBuilder, SqlPara> consumer) {
        if (ObjectUtil.isEmpty(fieldValue)) {
            return this;
        }
        Objects.requireNonNull(consumer).accept(whereBuilder, sqlPara);
        return this;
    }

    public LambdaQueryWrapper<T> apply(String applySql, Object... values) {
        whereBuilder.append(" ").append(applySql);
        Arrays.stream(values).forEach(sqlPara::addPara);
        return this;
    }

    public LambdaQueryWrapper<T> isNotNull(SFunction<T, ?> sFunction) {
        whereBuilder.append(" and ").append(getColumn(sFunction)).append(" is not null");
        return this;
    }

    public LambdaQueryWrapper<T> isNull(SFunction<T, ?> sFunction) {
        whereBuilder.append(" and ").append(getColumn(sFunction)).append(" is null");
        return this;
    }

    public LambdaQueryWrapper<T> isNull(String field) {
        whereBuilder.append(" and ").append(field).append(" is null");
        return this;
    }

    public LambdaQueryWrapper<T> isEmpty(SFunction<T, ?> sFunction) {
        whereBuilder.append(" and (").append(getColumn(sFunction)).append(" is null or ")
                .append(getColumn(sFunction)).append(" = '')");
        return this;
    }

    public LambdaQueryWrapper<T> orderDesc(SFunction<T, ?> fieldName) {
        String orderByPart = getColumn(fieldName) + " desc";
        //如果已经有排序条件了 就在前面要加个,
        if (CollectionUtil.isNotEmpty(orderByParts)) {
            orderByPart = "," + orderByPart;
        }
        orderByParts.add(orderByPart);
        return this;
    }

    public LambdaQueryWrapper<T> orderAsc(SFunction<T, ?> fieldName) {
        return orderAsc(getColumn(fieldName));
    }

    public LambdaQueryWrapper<T> orderAsc(String fieldName) {
        String orderByPart = fieldName + " asc";
        //如果已经有排序条件了 就在前面要加个,
        if (CollectionUtil.isNotEmpty(orderByParts)) {
            orderByPart = "," + orderByPart;
        }
        orderByParts.add(orderByPart);
        return this;
    }

    public LambdaQueryWrapper<T> limit(int i) {
        this.limit = i;
        return this;
    }


    public boolean exist() {
        selectFields.clear();
        selectFields.add(" 1");
        limit = 1;
        T t = DbUtil.findFirst(build());
        return t != null;
    }

    public Optional<T> selectOne() {
        if (this.returnEmpty) {
            return Optional.empty();
        }
        List<T> list = DbUtil.find(build());
        if (ObjectUtil.isEmpty(list)) {
            return Optional.empty();
        }
        Assert.isTrue(list.size() == 1, "查询结果数量不为1");
        return Optional.ofNullable(list.get(0));
    }

    public T one() {
        return selectOne().orElse(null);
    }

    public List<T> selectList() {
        if (this.returnEmpty) {
            return new ArrayList<>();
        }
        List<T> list = DbUtil.find(build());
        if (ObjectUtil.isEmpty(groupByParts)) {
            // group by后， 无数据也会返回一条数据，需进行处理
            if (list.size() == 1) {
                Collection<Object> values = JSONUtil.parseObj(JSONUtil.toJsonStr(list.get(0))).values();
                if (values.stream().allMatch(Objects::isNull)) {
                    return Collections.emptyList();
                }
            }
        }
        return list;
    }

    public List<T> list() {
        return selectList();
    }

//    public int update() {
//        Assert.notEmpty(updateFields, "更新字段不能为空");
//        SqlPara<T> sqlPara = buildUpdate();
//        return Db.update(sqlPara);
//    }

    public Long count() {
        selectFields = Lists.newArrayList("count(*) as num");
        return DbUtil.count(build());
    }

//    public Page<M> selectPage(Integer pageNumber, Integer pageSize) {
//        if (this.returnEmpty) {
//            return PageKit.emptyPage(pageNumber, pageSize);
//        }
//        return DAO.paginate(pageNumber, pageSize, build());
//    }


    public <K> Map<K, List<T>> listAndGroupMap(Function<T, K> keyFunction) {
        return ListKit.groupToMap(selectList(), keyFunction);
    }

    public <K, R> Map<K, List<R>> listAndGroupMap(Function<T, K> keyFunction, Function<T, R> valueFunction) {
        return ListKit.groupToMap(selectList(), keyFunction, valueFunction);
    }

    public <K> Map<K, T> listToMap(Function<T, K> keyFunction) {
        return ListKit.convertToMap(selectList(), keyFunction);
    }

    public <R> List<R> listAndConvert(Function<T, R> operator) {
        return ListKit.convert(selectList(), operator);
    }

    public <K, V> Map<K, V> listToMap(Function<T, K> keyFunction, Function<T, V> valueFunction) {
        return ListKit.convertToMap(selectList(), keyFunction, valueFunction);
    }

    public <K, R> Map<K, R> listMergeToMap(Function<T, K> keyFunction, BiFunction<T, R, R> mergeFunction, R initValue) {
        return ListKit.mergeToMap(selectList(), keyFunction, mergeFunction, initValue);
    }

//    public int delete() {
//        String sql = StrUtil.format("delete from {} {}", tableInfo.getTableName(), buildWhere());
//        if (Objects.nonNull(limit)) {
//            sql += " limit " + limit;
//        }
//        return Db.delete(sql, sqlPara.getPara());
//    }


    private SqlPara<T> build() {
        if (!orderByParts.isEmpty()) {
            this.appendSqlPart(" order by ");
            orderByParts.forEach(this::appendSqlPart);
        }
        String sql = StrUtil.format("select {} from {} {}", buildSelectFields(), tableInfo.getTableName(), buildWhere());
        if (ObjectUtil.isNotEmpty(groupByParts)) {
            sql += " group by " + StrUtil.join(",", groupByParts);
        }
        if (Objects.nonNull(limit)) {
            sql += " limit " + limit;
        }
        return sqlPara.setSql(sql);
    }

    private SqlPara<T> buildUpdate() {
        SqlPara<T> updateSqlPara = new SqlPara<>();
        List<String> fieldStr = MapKit.convertToList(updateFields, (field, value) -> {
            if (value instanceof SFunction) {
                @SuppressWarnings("unchecked")
                SFunction<T, ?> sFunction = (SFunction<T, ?>) value;
                value = getColumn(sFunction);
                return StrUtil.format("{} = {}", field, value);
            }
            updateSqlPara.addPara(value);
            return StrUtil.format("{} = ?", field);
        });
        Arrays.stream(sqlPara.getPara()).forEach(updateSqlPara::addPara);
        String sql = StrUtil.format("update {} set {} {}", tableInfo.getTableName(), StrUtil.join(",", fieldStr), buildWhere());
        if (Objects.nonNull(limit)) {
            sql += " limit " + limit;
        }
        return updateSqlPara.setSql(sql);
    }

    private String buildSelectFields() {
        if (ObjectUtil.isEmpty(selectFields)) {
            return ListKit.getFieldStr(CacheMap.getEntityMethods(entityClass).keySet(), Function.identity(), StrUtil.COMMA);
        }
        return StrUtil.join(",", selectFields);
    }


    private String buildWhere() {
        String where = whereBuilder.toString().trim().toLowerCase();
        if (!where.startsWith("where")) {
            where = where.replaceFirst("and", "where");
        }
        return where;
    }

    private void appendSqlPart(String sqlPart) {
        whereBuilder.append(" ").append(Objects.requireNonNull(sqlPart));
    }


    private String getColumn(SFunction<T, ?> sFunction) {
        String fieldName = methodToProperty(LambdaUtils.resolve(sFunction).getImplMethodName());
        fieldName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, fieldName);
        // 检查字段名是否存在
        Assert.isTrue(tableInfo.getColumnNameSet().contains(fieldName), "表{},不存在字段:{}", tableInfo.getTableName(), fieldName);
        return fieldName;
    }

    private static String methodToProperty(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        } else {
            throw new IllegalArgumentException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
        }

        if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }

        return name;
    }

    public Optional<T> oneOpt() {
        return selectOne();
    }
}